package com.chenwc.serialassistant.util.serial;

import com.chenwc.serialassistant.threadpool.supplier.GetSerialPortDataSupplier;
import com.chenwc.serialassistant.util.LoadConfigInfo;
import gnu.io.*;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
 * 串口工具类
 *
 * @author chenwc
 */
public class SerialPortUtil {

    private final static Logger logger = LoggerFactory.getLogger(SerialPortUtil.class);

    /**
     * 读取配置文件
     */
    private final static LoadConfigInfo LOAD_CONFIG_INFO = new LoadConfigInfo();

    /**
     * 获得系统可用的端口名称列表(COM0、COM1、COM2等等)
     *
     * @return List可用端口名称列表
     */
    @SuppressWarnings("unchecked")
    public static List<String> getSerialPortList() {
        List<String> systemPorts = new ArrayList<>();
        //获得系统可用的端口
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            //获得端口的名字
            String portName = portList.nextElement().getName();
            systemPorts.add(portName);
        }
        return systemPorts;
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        CustomSerialPort parameter = new CustomSerialPort(serialPortName);
        return openSerialPort(parameter);
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @param baudRate       波特率
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName, int baudRate)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        CustomSerialPort parameter = new CustomSerialPort(serialPortName, baudRate);
        return openSerialPort(parameter);
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @param baudRate       波特率
     * @param timeout        串口打开超时时间
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName, int baudRate, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        CustomSerialPort parameter = new CustomSerialPort(serialPortName, baudRate);
        return openSerialPort(parameter, timeout);
    }

    /**
     * 打开串口
     *
     * @param parameter 串口参数
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(CustomSerialPort parameter)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        return openSerialPort(parameter, 2000);
    }

    /**
     * 打开串口
     *
     * @param parameter 串口参数
     * @param timeout   串口打开超时时间
     * @return SerialPort串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(CustomSerialPort parameter, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        //通过端口名称得到端口
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(parameter.getSerialPortName());
        //打开端口，（自定义名字，打开超时时间）
        CommPort commPort = portIdentifier.open(parameter.getSerialPortName(), timeout);
        //判断是不是串口
        if (commPort instanceof SerialPort) {
            SerialPort serialPort = (SerialPort) commPort;
            //设置串口参数（波特率，数据位8，停止位1，校验位无）
            serialPort.setSerialPortParams(parameter.getBaudRate(), parameter.getDataBits(), parameter.getStopBits(), parameter.getParity());
            return serialPort;
        } else {
            //是其他类型的端口
            throw new NoSuchPortException();
        }

    }

    /**
     * 检查串口是否打开
     *
     * @param serialPort 串口
     * @return true已打开，false关闭
     */
    public static boolean isOpenSerialPort(SerialPort serialPort) {
        return serialPort != null;
    }

    /**
     * 关闭串口
     *
     * @param serialPort 要关闭的串口对象
     */
    public static void closeSerialPort(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.close();
        }
    }

    /**
     * 向串口发送数据
     *
     * @param serialPort  串口对象
     * @param data        发送的数据
     * @param charsetName 发送数据的字符编码
     */
    public static void sendData(SerialPort serialPort, byte[] data, String charsetName) {

        OutputStreamWriter osw = null;
        try {
            //获得串口的输出流
            osw = new OutputStreamWriter(serialPort.getOutputStream(), charsetName);
            osw.write(new String(data, charsetName));
            osw.flush();
        } catch (IOException e) {
            throw new RuntimeException("串口发送数据时 IO 异常！");
        } finally {
            IOUtils.closeQuietly(osw);
        }
    }

    /**
     * 向串口发送数据
     *
     * @param serialPort  串口对象
     * @param data        发送的数据
     * @param charsetName 发送数据的字符编码
     */
    public static void sendData(SerialPort serialPort, String data, String charsetName) {

        OutputStreamWriter osw = null;
        try {
            //获得串口的输出流
            osw = new OutputStreamWriter(serialPort.getOutputStream(), charsetName);
            osw.write(data);
            osw.flush();
        } catch (IOException e) {
            throw new RuntimeException("串口发送数据时 IO 异常！");
        } finally {
            IOUtils.closeQuietly(osw);
        }
    }

    /**
     * 向串口发送数据
     *
     * @param serialPort 串口对象
     * @param data       发送的数据
     */
    public static void sendData(SerialPort serialPort, byte[] data) {

        OutputStream os = null;
        try {
            //获得串口的输出流
            os = serialPort.getOutputStream();
            os.write(data);
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException("串口发送数据时 IO 异常！");
        } finally {
            IOUtils.closeQuietly(os);
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 要读取的串口
     * @return 读取的数据
     */
    public static byte[] readData(SerialPort serialPort, ExecutorService executorService) {

        String strBytes = "";

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(
                        new GetSerialPortDataSupplier(serialPort), executorService)
                .whenComplete((result, e) -> {
                    //执行线程执行完以后的操作。
                    logger.info("接收串口数据异步线程执行完成！");
                }).exceptionally((e) -> {
                    //打印异常
                    logger.error("接收串口数据异步线程执行异常！");
                    return "Receive SerialPort Data Exception";
                });
        try {
            strBytes = completableFuture.get();
        } catch (Exception e) {
            logger.error("Error: {}", e.getMessage());
        }

        try {
            return strBytes.getBytes(LOAD_CONFIG_INFO.getSerialPortReadDataCharsetName());
        } catch (UnsupportedEncodingException e) {
            logger.error("Error: {}", e.getMessage());
        }
        return null;
    }

    /**
     * 给串口设置监听
     *
     * @param serialPort serialPort 要读取的串口
     * @param listener   SerialPortEventListener监听对象
     * @throws TooManyListenersException 监听对象太多
     */
    public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) throws TooManyListenersException {
        //给串口添加事件监听
        serialPort.addEventListener(listener);
        //串口有数据监听
        serialPort.notifyOnDataAvailable(true);
        //中断事件监听
        serialPort.notifyOnBreakInterrupt(true);
    }
}
